﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Pets.Abstract;
using System.Reflection;
namespace Pets.Concrete
{
    /// <summary>
    /// Class for save\load from file
    /// </summary>
    public sealed class SerializerXML : ISerializer
    {
        #region Interface's methods

        public void Save<T>(string filename, List<T> data) where T: class, new()
        {
            StreamWriter sw = new StreamWriter(filename, false);

            /// Start of collections 
            T obj_that_give_type = new T();
            if (data != null)
            {
                sw.WriteLine(TagDecorator.ToOpenTag(obj_that_give_type.GetType().ToString()));
            }
            else
            {
                throw new ArgumentNullException();
            }
            List<string> out_big_list = new List<string>();
            foreach(T obj in data)
            {
            out_list = null;
            PrintXMLToList(obj,null);
            foreach (string s in out_list) out_big_list.Add(s);
            }

            foreach (string line in out_big_list) sw.WriteLine(line);
            sw.WriteLine(TagDecorator.ToCloseTag(obj_that_give_type.GetType().ToString()));
            sw.Close();

        }

        /// <summary>
        /// Load collection from source
        /// </summary>
        public List<T> Load<T>(string filename) where T: class, new()
        {
            ///From file to list
            StreamReader sr = new StreamReader(filename);
            List<string> tags_list = new List<string>();
            string line;

            while ((line = sr.ReadLine()) != null)
            {
                tags_list.Add(line);
            }

            ///Intalize objects
            List<T> objects_list = new List<T>();
            this.source = tags_list;
            this.isCheked = new bool[this.source.Count];
            int open_position, close_position;
            open_position = 1;
            close_position = open_position + 1;

            ///Search in list
            while (close_position != tags_list.Count - 2)
            {
                ///Search position of close tag to open
                close_position = TagDecorator.FindCloseTag(tags_list, open_position, TagDecorator.GetName(tags_list[open_position]));
                
                ///Building xml-tree
                this.FindChildren(open_position, close_position, 1, null);
                
                ///Сreate object and write to Array
                T cat_or_dog = this.BuildTree(this.tree_tags,null) as T;
                objects_list.Add(cat_or_dog);
                open_position = close_position + 1;
            }
            return objects_list;
        }

        #endregion

        #region Private methods

        private ClassTag tree_tags;

        ///True, if string is looked
        private bool[] isCheked;

        private List<string> source;

        private List<string> out_list;

        private void PrintXMLToList(object obj, string parent_name)
        {
            if (out_list == null) out_list = new List<string>();
            string Name = string.Empty;
            if (parent_name == null)
            {
                Name = obj.GetType().ToString();
            }
            else
            {
                Name = parent_name;
            }
            out_list.Add(TagDecorator.ToOpenTag(Name));
            ///Rekursion
            foreach (System.Reflection.PropertyInfo property in obj.GetType().GetProperties())
            {
                object value = property.GetValue(obj, null);
                if (value.GetType() == typeof(int) || value.GetType() == typeof(string))
                {
                    out_list.Add(TagDecorator.ToDoubleTag(property.Name, value.ToString()));
                }
                else
                {
                    PrintXMLToList(value, property.Name);
                }
            }
            out_list.Add(TagDecorator.ToCloseTag(Name));

        }

        ///<summary>
        ///Building xml-tree and creates tags
        ///</summary>
        private void FindChildren(int start, int end, int lvl, ClassTag parent)
        {
            ///Start intalize
            if (lvl == 1)
            {
                this.Intalize(start);
                isCheked[start] = true;
                isCheked[end] = true;
                this.FindChildren(start + 1, end - 1, 2, this.tree_tags);
            }

            /// Seerch children elements
            for (int i = start; i <= end; i++)
            {
                if (isCheked[i] != true)
                {
                    isCheked[i] = true;
                    string name = TagDecorator.GetName(this.source[i]);

                    ///Tag is class or is property
                    bool isClass = true;
                    if (this.source[i].IndexOf('/') > 0) isClass = false;

                    if (isClass)
                    {
                        ///Set values
                        ClassTag this_tag = new ClassTag();
                        this_tag.Name = name;
                        this_tag.Level = lvl;
                        this_tag.Parent = parent;
                        if (parent.Children == null)
                        {
                            parent.Children = new List<ITag>();
                        }
                        parent.Children.Add(this_tag);

                        ///Search close tag
                        int new_end = TagDecorator.FindCloseTag(this.source, i + 1, this_tag.Name);
                        isCheked[new_end] = true;

                        /// Searcg children elemenets
                        this.FindChildren(i + 1, new_end - 1, lvl + 1, this_tag);
                    }
                    else
                    {
                        ///<summary>
                        /// Присвоение свойств
                        ///</summary>
                        PropTag this_tag = new PropTag();
                        this_tag.Name = name;
                        this_tag.Level = lvl;
                        this_tag.Parent = parent;
                        this_tag.Value = TagDecorator.GetValue(this.source[i]);
                        if (parent.Children == null)
                        {
                            parent.Children = new List<ITag>();
                        }
                        parent.Children.Add(this_tag);
                    }
                }
            }
        }
        
        ///<summary>
        ///Начальная инициализация хмл-дерева, создание корневого елемента
        ///</summary>
        private void Intalize(int open_tag_pos)
        {
            this.tree_tags = new ClassTag();
            this.tree_tags.Name = TagDecorator.GetName(this.source[open_tag_pos]);
            this.tree_tags.Level = 1;
            this.tree_tags.Parent = null;

            for (int i = open_tag_pos; i < isCheked.Length; i++) this.isCheked[i] = false;
        }

        /// <summary>
        /// Создание объекта Animal из хмл-дерева
        /// </summary>
        private object BuildTree(ClassTag parent, Type parent_t)
        {
            /// <summary>
            /// Определение типа объекта parent и создание его экземпляра с пустым конструктором
            /// </summary>
            Type parent_type = null;
            if (parent_t != null)
            {
                parent_type = parent_t;
            }
            else 
            {
                parent_type = Type.GetType(parent.Name);
            }
             
            object this_class = Activator.CreateInstance(parent_type);

            /// <summary>
            /// Поиск дочерних елементов созданного класса.
            /// Если тип дочернего свойства примитивный - устанавливаем его,
            /// иначе рекурсивно вызываем метод.
            /// </summary>
            foreach (ITag tag in parent.Children)
            {
                if (tag.GetType() == typeof(PropTag))
                {
                    Type t = this_class.GetType().GetProperty(tag.Name).PropertyType;
                    if (t == typeof(System.String))
                    {
                        this_class.GetType().GetProperty(tag.Name).SetValue(this_class, (tag as PropTag).Value, null);
                    }
                    if (t == typeof(System.Int32))
                    {
                        this_class.GetType().GetProperty(tag.Name).SetValue(this_class, Convert.ToInt32((tag as PropTag).Value), null);
                    }
                }
                else
                {
                    this_class.GetType().GetProperty(tag.Name).SetValue(this_class, this.BuildTree(tag as ClassTag,
                    this_class.GetType().GetProperty(tag.Name).PropertyType), null);
                }
            }
            return this_class;

        }

        #endregion

    }
}